import threading
import codecs

from irc.client import ip_quad_to_numstr, ip_numstr_to_quad

from kivy.logger import Logger
      
from amgineirc.viewModel.channels.clientEventHandlerViewModel import ClientEventHandlerViewModel
from amgineirc.model.amgine.amgine import Amgine


class RuleVMHelper(ClientEventHandlerViewModel):

    status_none = 0
    status_waiting_for_accept = 1
    status_accepting = 1
    status_sending = 2
    status_receiving = 2
    
    ruleshake_request = 'ruleshake-request'
    ruleshake_payload = 'payload'

    def __init__(self, view_model, dcc_connection):
        self._view_model = view_model
        self._ruleshake_rule = self._view_model._appdata['rules'].get(self._view_model._rule_name)
        self._status = SendRuleVMHelper.status_none
        ClientEventHandlerViewModel.__init__(self,
                                             dcc_connection,
                                             None,
                                             self._view_model._appdata)

    @property
    def server(self):
        if self._server is None and self._connection is not None:
            self._server = self._connection.peeraddress
        return self._server
                                             
    @property
    def busy(self):
        return self._status != RuleVMHelper.status_none

    def _save(self):
        pass        
        
    def handle_on_disconnect(self):
        self._status = RuleVMHelper.status_none
        self._connection = None

    def error_out(self):
        Logger.debug('error out')                                                        
        if self.connected:
            self.connection.disconnect()
            
    def stop(self):
        if self.connected:
            self.connection.disconnect()
            
    def _inform_user(self, task):
        target = self._view_model.target
        msg = 'the new rule has been %s %s' % (task, target)
        self._view_model.handle_client_event(target,
                                            'input',
                                            self._view_model.server,
                                            self._view_model._nick,
                                            [msg])
        

class ReceiveRuleVMHelper(RuleVMHelper):
    def __init__(self, view_model, peer_ip, peer_port):
        dcc_connection = view_model._appdata['client'].dcc_connect(peer_ip,
                                                                   peer_port,
                                                                   dcctype='raw')
        RuleVMHelper.__init__(self, view_model, dcc_connection)
        self._ruleshake_read_count = 0
        self._chunk_size = 0
        self._chunk_buffer = []
        self._read_list = []
        self._finish_timer = None
        self._download_lists = []
        
    def accept(self):
        self._status = SendRuleVMHelper.status_accepting
        self._confirm()
        
    def _confirm(self):
        length = str(len(self._chunk_buffer))
        msgs = self._view_model._amgine.apply_handshake_send_rules(length)
        try:
            self._connection.send_bytes(msgs[0])
        except Exception as e:
            self._view_model.handle_client_event(self._view_model.target,
                                                'exception',
                                                self._view_model.server,
                                                self._view_model._nick,
                                                [repr(e)])
            self.error_out()
            
    def _receive_chunk(self, arg):
        bl = [ord(i) for i in arg]
        ba = bytearray(bl)
        
        if self._chunk_size == 0:
            if len(ba) > 10 \
            and ba[0] == 0 and ba[1] == 1 and ba[2] == 2 \
            and ba[6] == 2 and ba[7] == 1 and ba[8] == 0:
                ba.pop(0) # 0
                ba.pop(0) # -1
                ba.pop(0) # -2
                gsb = ba.pop(0)
                msb = ba.pop(0)
                lsb = ba.pop(0)
                ba.pop(0) # -2
                ba.pop(0) # -1
                ba.pop(0) # 0
                self._chunk_size = (gsb * 65535) + (msb * 256) + lsb
            else:
                # ignore this chunk
                # wait for the next start chunk with length
                Logger.debug('unknown chunk of size %i' % (len(ba)))
                return
        # process the chunk
        try:
            self._chunk_size -= len(ba)
            clean_list = self._view_model._amgine.apply_dcc_file_receive_rules(ba)
            self._chunk_buffer.extend(clean_list)
            if self._chunk_size == 0:
                self._save_chunk_to_lists()
                self._inform_user('downloaded from') 
                # save the rule
                self._save()
                self._confirm()
                self._finish()
            else:
                # only got part of a send so don't confirm
                pass
        except Exception as e:
            Logger.debug('_receive_chunk: %s' % (repr(e)))
            self.error_out()
            
    def _save_chunk_to_lists(self):
        len_chunk = len(self._chunk_buffer)
        if len_chunk % 256 == 0:
            start = 0
            while start < len_chunk:
                end = start + 256
                self._download_lists.append(self._chunk_buffer[start:end])
                start = end
        
    def handle_client_event(self, evtarget, evtype, evserver, evsource, evargs):
        if evtype == 'dccmsg':
            if len(evargs[0]) > 0:
                self._receive_chunk(evargs[0])

    def _save(self):
        self._ruleshake_rule.set_rule_1_keys(self._download_lists)
        self._view_model._appdata['rules'].replace_rule(self._ruleshake_rule,
                                                        self._view_model._rule_name)
        
    def _finish(self):
        self._finish_timer = threading.Timer(5.0, self.disconnect)
        self._finish_timer.start()
        
    def disconnect(self):
        if self._finish_timer is not None:
            self._finish_timer.cancel()
        if self.connected:
            self._connection.disconnect()
            
    def handle_on_connect(self, connection):
        Logger.debug('connected')
        self._status = RuleVMHelper.status_receiving
          

class SendRuleVMHelper(RuleVMHelper):
    def __init__(self, view_model, dcc_connection):
        RuleVMHelper.__init__(self, view_model, dcc_connection)
        self._data_list = []
        self._key_list = []

    def request(self):
        # setup
        if self._status == RuleVMHelper.status_none:
            # initialize variables
            self._status = RuleVMHelper.status_waiting_for_accept
            # the new rule to replace the current rule
            self._build_data()
            # inform the other user
            self._view_model.handle_view_send('I am sending a new rule to you.\nIt is being sent to you in the background.')
            # send the ruleshake request through the dcc chat socket
            text = str(len(self._data_list))
            message = '%s %s %i' % (SendRuleVMHelper.ruleshake_request,
                                       self._connection.localaddress,
                                       self._connection.localport)
            self._view_model.connection.privmsg(message)
            
    def _build_data(self):
        self._key_list = self._ruleshake_rule.get_rule_1_random_keys()
        for i in self._key_list:
            self._data_list.extend(i)

    def _send_chunk(self):
        # now send a payload
        count = len(self._data_list)
        self._inform_user('uploaded to') 
        chunk = self._view_model._amgine.apply_dcc_file_send_rules(self._data_list)
        n = len(chunk)
        gsb = n / 65535
        n %= 65535 
        msb = n / 256
        lsb = n % 256
        chunk.insert(0, 0) 
        chunk.insert(0, 1) 
        chunk.insert(0, 2) 
        chunk.insert(0, lsb) 
        chunk.insert(0, msb) 
        chunk.insert(0, gsb) 
        chunk.insert(0, 2) 
        chunk.insert(0, 1) 
        chunk.insert(0, 0)
        self._connection.send_bytes(chunk)
        
    def handle_client_event(self, evtarget, evtype, evserver, evsource, evargs):
        if evtype == 'dccmsg':
            # all messages are confirmations
            try:
                s_nbytes = self._view_model._amgine.apply_handshake_receive_rules(evargs[0])            
                count = int(s_nbytes)
                if count == 0:
                    self._send_chunk()
                elif count == len(self._data_list):
                    self._save()
                    self._finish()
                else: 
                    self.error_out()
            except Exception as e:
                Logger.debug('exception %s' % (repr(e)))
                self.error_out()
        
    def _finish(self):
        # save the rule
        self._view_model._appdata['rules'].replace_rule(self._ruleshake_rule,
                                                        self._view_model._rule_name)
        # the receiver sent back that it received all data
        # after it saved the rule
        # so go ahead and disconnect
        if self.connected:
            self.connection.disconnect()

    def _save(self):
        # save the rule
        self._ruleshake_rule.set_rule_1_keys(self._key_list)
        self._view_model._appdata['rules'].replace_rule(self._ruleshake_rule,
                                                        self._view_model._rule_name)
            
    def handle_on_connect(self, connection):
        self._status = RuleVMHelper.status_sending
